﻿using System;
using System.Collections.Generic;
using System.DirectoryServices.ActiveDirectory;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Web.UI;
using System.Web.UI.WebControls;
using Library;

namespace DirectoryWebCS
{
  /// <summary>
  /// ドメイン関連情報画面のフォームです。
  /// </summary>
  public partial class DomainInfo : Page
  {
    #region イベントハンドラ
    protected void Page_Load(object sender, EventArgs e)
    {
      if (this.IsPostBack == false)   //初期表示の時
      {
        using (var domain = DirectoryAccess.GetDomain())  //ドメインを取得
        {
          if (domain != null)
          {
            this.ShowInfo(domain);  //ドメイン関連情報を表示
          }
        }
      }
    }

    protected void GCListBox_SelectedIndexChanged(object sender, EventArgs e)
    {
      this.GCSiteLabel.Text = ((IList<string>)ViewState["GCSites"])[this.GCListBox.SelectedIndex];
    }

    protected void SiteListBox_SelectedIndexChanged(object sender, EventArgs e)
    {
      var sites = (Dictionary<string, Tuple<List<string>,
        Dictionary<string, Tuple<int, TimeSpan, ActiveDirectoryTransportType>>, List<string>>>)ViewState["Sites"];
      var site = sites[this.SiteListBox.SelectedValue];
      this.SetSite(site);   //サイトを設定
    }

    protected void SiteLinkListBox_SelectedIndexChanged(object sender, EventArgs e)
    {
      var sites = (Dictionary<string, Tuple<List<string>,
        Dictionary<string, Tuple<int, TimeSpan, ActiveDirectoryTransportType>>, List<string>>>)ViewState["Sites"];
      var site = sites[this.SiteListBox.SelectedValue];
      var siteLinkInfo = site.Item2[this.SiteLinkListBox.SelectedValue];
      this.SetSiteLinkInfo(siteLinkInfo);   //サイトリンク情報を設定
    }

    protected void DCListBox_SelectedIndexChanged(object sender, EventArgs e)
    {
      this.SetDCInfo();   //ドメインコントローラー情報を設定
    }

    protected void Button1_Click(object sender, EventArgs e)
    {
      Response.Redirect("Main.aspx");
    }
    #endregion

    #region プライベートメソッド
    /// <summary>
    /// ドメイン関連情報を表示します。
    /// </summary>
    /// <param name="adDomain">ドメイン。</param>
    private void ShowInfo(Domain adDomain)
    {
      //****************************** フォレスト ******************************
      var forest = adDomain.Forest;
      this.ForestLabel.Text = forest.Name;
      this.ForestModeLabel.Text = forest.ForestMode.ToString();
      this.RootDomainLabel.Text = forest.RootDomain.Name;
      this.SetDataSource(this.GCListBox,
        forest.GlobalCatalogs.Cast<GlobalCatalog>().Select(gc => gc.Name).ToList());  //データソースを設定

      var gcSites = forest.GlobalCatalogs.Cast<GlobalCatalog>().Select(gc => gc.SiteName).ToList();   //サイトリスト
      ViewState["GCSites"] = gcSites;
      this.GCSiteLabel.Text = gcSites[0];
      this.SetDataSource(this.DomainListBox, 
        forest.Domains.Cast<Domain>().Select(dom => dom.Name).ToList());  //データソースを設定

      //****************************** サイト ******************************
      //サイト名/サイト項目
      var sites = new Dictionary<string, Tuple<List<string>,
        Dictionary<string, Tuple<int, TimeSpan, ActiveDirectoryTransportType>>, List<string>>>();
      //サーバのリスト, Dictionary(サイトリンク名, Tuple(コスト, 複製間隔, 転送の種類)), サブネットのリスト
      Tuple<List<string>, Dictionary<string, Tuple<int, TimeSpan, ActiveDirectoryTransportType>>, List<string>> siteItem;

      foreach (ActiveDirectorySite site in forest.Sites)
      {
        var servers = site.Servers.Cast<DirectoryServer>().Select(server => server.Name).ToList();
        var siteLinks = site.SiteLinks.Cast<ActiveDirectorySiteLink>().ToDictionary(sl => sl.Name,
          sl => new Tuple<int, TimeSpan, ActiveDirectoryTransportType>(
            sl.Cost, sl.ReplicationInterval, sl.TransportType));
        var subnets = site.Subnets.Cast<ActiveDirectorySubnet>().Select(subnet => subnet.Name).ToList();

        siteItem = new Tuple<List<string>, 
          Dictionary<string, Tuple<int, TimeSpan, ActiveDirectoryTransportType>>, 
          List<string>>(servers, siteLinks, subnets);
        sites.Add(site.Name, siteItem);
      }
      ViewState["Sites"] = sites;
      siteItem = sites[forest.Sites[0].Name];
      this.SetDataSource(this.SiteListBox, sites.Keys);   //データソースを設定
      this.SetSite(siteItem);   //サイトを設定

      //****************************** ドメイン ******************************
      this.DomainLabel.Text = adDomain.Name;
      this.DomainModeLabel.Text = adDomain.DomainMode.ToString();
      //ドメインコントローラー名/Tuple(OS, IPアドレス, サイト名)
      var dcInfos = adDomain.DomainControllers.Cast<DomainController>().ToDictionary(
        dc => dc.Name.Substring(0, dc.Name.IndexOf('.')),
        dc => new Tuple<string, string, string>(dc.OSVersion, GetDomainControllerIPAddress(dc), dc.SiteName));
      ViewState["DCInfos"] = dcInfos;
      this.SetDataSource(this.DCListBox, dcInfos.Keys);   //データソースを設定
      this.SetDCInfo();   //ドメインコントローラー情報を設定

      //****************************** FSMO ******************************
      this.SchemaLabel.Text = forest.SchemaRoleOwner.Name;
      this.NamingLabel.Text = forest.NamingRoleOwner.Name;
      this.RIDLabel.Text = adDomain.RidRoleOwner.Name;
      this.PDCLabel.Text = adDomain.PdcRoleOwner.Name;
      this.InfraLabel.Text = adDomain.InfrastructureRoleOwner.Name;
    }

    /// <summary>
    /// ドメインコントローラーの IP アドレスを取得します。
    /// </summary>
    /// <param name="dc">ドメインコントローラー。</param>
    /// <returns>ドメインコントローラーの IP アドレス。</returns>
    private string GetDomainControllerIPAddress(DomainController dc)
    {
      if (dc.IPAddress.Contains("."))   //IPv4の時
      {
        return dc.IPAddress;
      }

      var ip = IPAddress.Parse(dc.IPAddress);
      if (IPAddress.IsLoopback(ip))   //ループバックの時
      {
        var addresses = Dns.GetHostAddresses(String.Empty);   //ローカルホストのIPアドレス
        ip = addresses.FirstOrDefault(address => address.AddressFamily == AddressFamily.InterNetwork);  //IPv4のアドレス
        if (ip == null)   //IPv4のアドレスがない時
        {
          ip = addresses[0];  //先頭のIPv6のアドレス
        }
        return ip.ToString();
      }
      return dc.IPAddress;
    }

    /// <summary>
    /// リストボックスにデータソースを設定します。
    /// </summary>
    /// <param name="listBox">リストボックス。</param>
    /// <param name="dataSource">データソースとなる文字列のコレクション。</param>
    private void SetDataSource(ListBox listBox, IEnumerable<string> dataSource)
    {
      listBox.DataSource = dataSource;
      listBox.DataBind();
      listBox.Items[0].Selected = listBox.AutoPostBack;
    }

    /// <summary>
    /// サイトを設定します。
    /// </summary>
    /// <param name="sites">サイト項目。</param>
    private void SetSite(Tuple<
      List<string>, Dictionary<string, Tuple<int, TimeSpan, ActiveDirectoryTransportType>>, List<string>> site)
    {
      this.SetDataSource(this.ServerListBox, site.Item1);   //データソースを設定
      this.SetDataSource(this.SiteLinkListBox, site.Item2.Keys);  //データソースを設定

      var siteLinkInfo = site.Item2[this.SiteLinkListBox.SelectedValue];
      this.SetSiteLinkInfo(siteLinkInfo);   //サイトリンク情報を設定
      this.SetDataSource(this.SubnetListBox, site.Item3);   //データソースを設定
    }

    /// <summary>
    /// サイトリンク情報を設定します。
    /// </summary>
    /// <param name="siteLinkInfo">サイトリンク情報。</param>
    private void SetSiteLinkInfo(Tuple<int, TimeSpan, ActiveDirectoryTransportType> siteLinkInfo)
    {
      this.CostLabel.Text = siteLinkInfo.Item1.ToString();
      this.RepliLabel.Text = siteLinkInfo.Item2.ToString();
      this.TransportLabel.Text = siteLinkInfo.Item3.ToString();
    }

    /// <summary>
    /// ドメインコントローラー情報を設定します。
    /// </summary>
    private void SetDCInfo()
    {
      var dcInfo = ((Dictionary<string, Tuple<string, string, string>>)ViewState["DCInfos"])[this.DCListBox.SelectedValue];
      this.OSLabel.Text = dcInfo.Item1;
      this.IPLabel.Text = dcInfo.Item2;
      this.DCSiteLabel.Text = dcInfo.Item3;
    }
    #endregion
  }
}